Skip to content

feat(meetings): calendar-triggered auto-join prompt + Meeting Assistant settings UI#3721

Merged
YellowSnnowmann merged 11 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/gmeet-autojoin
Jun 17, 2026
Merged

feat(meetings): calendar-triggered auto-join prompt + Meeting Assistant settings UI#3721
YellowSnnowmann merged 11 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/gmeet-autojoin

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Watch Google Calendar in the heartbeat planner and proactively prompt the user to add OpenHuman to upcoming Meet calls, gated by heartbeat.notify_meetings.
  • Fix lost Meet URL: PendingEvent now carries a dedicated meeting_url (separate from deep_link, which may point at a calendar details page), extracted from hangoutLink / conferenceData / location.
  • Route by auto_join_policy: Never → skip, Always → auto-join + transparency notification, AskEachTimeCoreNotificationCard with Join (listen) / Join (active) / Skip / Always-join actions.
  • New RPC openhuman.agent_meetings_notification_action ({notification_id, action_id, payload} → {ok}) handling join / skip / always_join.
  • New Meeting Assistant settings panel (auto-join, auto-summarize, listen-only default, transcript ingestion), persisted via MeetSettingsPatch.
  • New DomainEvents MeetingSessionCreated + MeetingAutoJoinTriggered; full i18n across all 14 locales.

Problem

collect_calendar_meetings stored htmlLink in deep_link when an event had both links, so the Meet URL was lost and the assistant could never act on an upcoming call. There was also no UI to control auto-join / auto-summarize / listen-only / transcript-ingestion behavior.

Solution

  • Detection (core): collect_calendar_events_recursive extracts the join URL via extract_meeting_url_from_map (matches known meeting host patterns through is_meeting_url) and populates PendingEvent.meeting_url. agent_meetings/calendar.rs adds auto_join_policy_owns_notification + handle_calendar_meeting_candidate to filter Meet meetings and route by policy.
  • Prompt flow: planner forwards imminent meetings to the auto-join policy, suppressing the generic plain card when the AskEachTime prompt owns the notification. Honors the ~30-min lead window + happening-now check; reuses existing dedup (fingerprint/overlap_key).
  • Action handling: handle_notification_action (ops.rs) resolves join (listen-only + correlation_id), skip (session → Ended), always_join (flip config + join).
  • Frontend: CoreNotificationCard renders localized action buttons (ACTION_LABEL_KEYS, primary/secondary styling) and dispatches over callCoreRpc; actions threaded through nativeNotifications/service.ts.
  • Settings: MeetSettingsPatch extended with auto_join_policy / auto_summarize_policy / listen_only_default / ingest_backend_transcripts; MeetingSettingsPanel wired into settings route registry + nav. Default ask_each_time — no silent joins.

Submission Checklist

  • Tests added or updated — Rust: URL extraction (happy + Hangout/conferenceData/Zoom-location edge cases), policy routing branches (ask/always/never), notification-action variants, events_tests.rs, ops_tests.rs. Vitest: CoreNotificationCard.test.tsx (button states + read state + RPC dispatch), MeetingSettingsPanel.test.tsx (each toggle/radio reads+writes config), config.test.ts, nativeNotifications/service.test.ts.
  • Diff coverage ≥ 80% — run pnpm test:coverage + pnpm test:rust locally to confirm.
  • Coverage matrix updated — update docs/TEST-COVERAGE-MATRIX.md (or mark N/A).
  • All affected feature IDs listed under ## Related.
  • No new external network dependencies introduced (mock backend used).
  • Manual smoke checklist updated if this touches release-cut surfaces.
  • Linked issues closed via Closes #NNNN in ## Related.

Impact

  • Platform: desktop (Tauri). Calendar detection runs in the core heartbeat; no new network deps.
  • Privacy/security: auto-join opt-in, default ask_each_time; Always still posts a transparency notification. Listen-only configurable. No secrets/PII logged (namespaced debug).
  • Migration: new config fields default-initialized; existing configs unaffected.

Related

Summary by CodeRabbit

Release Notes

  • New Features
    • Added actionable meeting notifications (join modes, skip, and related auto-join options), including “read” handling to prevent re-clicking after success.
    • Introduced a Meetings settings panel for automatic joining, post-call summarization, listen-only default, and backend transcript ingestion, and surfaced core actionable notifications in the notification center.
  • Localization
    • Added meeting notification and Meetings settings translations across all supported languages.
  • Tests
    • Added coverage for notification action behavior and meeting settings loading, saving, error states, and non-Tauri fallback.

…rompts

Introduces the CoreNotificationCard component to handle core-originated notifications with action buttons, specifically for meeting auto-join prompts. This includes tests to ensure proper rendering, action dispatching, and error handling. Additionally, updates to the notification center to display these notifications at the top, along with necessary localization strings for various languages.
…CoreNotificationCard

Updated the logging mechanism in CoreNotificationCard to use the debug library for better namespace management, aligning with project logging standards.
… CoreNotificationCard test

Refactored the test for CoreNotificationCard to streamline the dispatch of the notificationReceived action into a single line for improved readability. Additionally, updated the German translation for actionError to enhance formatting consistency.
…tionality

Introduces the Meeting Assistant settings panel, allowing users to configure auto-join policies, post-call summaries, listen-only modes, and transcript ingestion settings. Updates include new translations for various languages and integration into the settings route. Additionally, tests are added to ensure proper functionality and persistence of settings.
… integration

This update introduces a new function to handle calendar meeting candidates, applying user-defined auto-join policies. It includes improvements to the event collection process, allowing for the extraction of meeting URLs from calendar events. Additionally, the heartbeat planner now forwards imminent meetings to the auto-join policy, ensuring a seamless user experience. Tests have been updated to verify the correct handling of meeting URLs and related notifications.
…ates and read notifications

Added tests to verify button disabling during action RPCs and the visibility of unread notification indicators in the CoreNotificationCard. Updated the relative time calculation to ensure non-negative values. Improved logging in MeetingSettingsPanel for better debugging and added tests for successful persistence of settings.
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3099a773-ea5e-4b46-a08c-6c831e456887

📥 Commits

Reviewing files that changed from the base of the PR and between ea341a8 and 62584f3.

📒 Files selected for processing (5)
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • src/openhuman/agent_meetings/calendar.rs
  • src/openhuman/subconscious/heartbeat/planner/mod.rs
✅ Files skipped from review due to trivial changes (2)
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/fr.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/lib/i18n/ru.ts

📝 Walkthrough

Walkthrough

Implements Meeting Assistant PR-1 (calendar auto-join prompt) and PR-5 (settings UI). The backend gains meeting URL extraction in the heartbeat planner, a refactored calendar auto-join policy handler publishing MeetingSessionCreated/MeetingAutoJoinTriggered domain events, and an expanded MeetSettings config RPC surface. The frontend adds CoreNotificationCard with action buttons, a clearNotificationActions Redux action, a new MeetingSettingsPanel, and i18n strings across 14 locales.

Changes

Meeting Assistant: calendar auto-join, notification actions UI, and settings panel

Layer / File(s) Summary
Backend: expanded MeetSettings config contract and RPC handlers
src/openhuman/config/schemas/helpers.rs, src/openhuman/config/schemas/schema_defs.rs, src/openhuman/config/schemas/controllers.rs, src/openhuman/config/ops/ui.rs, src/openhuman/config/ops_tests.rs
MeetSettingsUpdate/MeetSettingsPatch extended with auto_join_policy, auto_summarize_policy, listen_only_default, and ingest_backend_transcripts; schema inputs/outputs expanded; controllers validate and map policy strings to enums, returning errors on invalid values; apply_meet_settings conditionally applies all new fields; ops tests cover full-patch and no-op branches.
Backend: MeetingSessionCreated and MeetingAutoJoinTriggered domain events
src/core/event_bus/events.rs, src/core/event_bus/events_tests.rs
Two new DomainEvent variants added with their payload fields; both routed to agent_meetings domain; variant_name() extended; domain routing and stable-name regression tests added.
Backend: PendingEvent.meeting_url and heartbeat planner URL extraction
src/openhuman/subconscious/heartbeat/planner/types.rs, src/openhuman/subconscious/heartbeat/planner/collectors.rs, src/openhuman/subconscious/heartbeat/planner/persistence.rs
PendingEvent gains optional meeting_url; collectors extract it from hangoutLink, conferenceData.entryPoints[].uri, or free-form location text using host-pattern allowlist; persistence serializes it; cron/notification collectors initialize to None; unit tests cover all extraction and rejection paths.
Backend: calendar auto-join policy orchestration
src/openhuman/agent_meetings/calendar.rs
handle_calendar_meeting_candidate extracted as public async function returning ownership flag; auto_join_policy_owns_notification predicate added; Never returns false, Always publishes MeetingAutoJoinTriggered and spawns join with listen_only_default, AskEachTime deduplicates by meet URL, creates MeetingSession, publishes MeetingSessionCreated, emits CoreNotificationEvent with join/skip/always-join actions; unit tests cover ownership predicate for all policies.
Frontend: expanded MeetSettings RPC types and Tauri command wrappers
app/src/utils/tauriCommands/config.ts, app/src/utils/tauriCommands/config.test.ts, app/src/test/setup.ts
MeetAutoJoinPolicy, MeetAutoSummarizePolicy, MeetSettings, MeetSettingsUpdate exported; openhumanUpdateMeetSettings and openhumanGetMeetSettings updated to use the new shapes; tests and global mock cover the full settings payload.
Frontend: notification actions Redux model and service pass-through
app/src/store/notificationSlice.ts, app/src/lib/nativeNotifications/service.ts, app/src/lib/nativeNotifications/__tests__/service.test.ts
clearNotificationActions reducer added to clear actions by notification id; CoreNotificationPayload extended with optional actions; service passes p.actions through on core_notification events and in test helper; service test verifies actions preserved in Redux store.
Frontend: CoreNotificationCard and NotificationCenter integration
app/src/components/notifications/CoreNotificationCard.tsx, app/src/components/notifications/CoreNotificationCard.test.tsx, app/src/components/notifications/NotificationCenter.tsx
CoreNotificationCard renders action buttons from ACTION_LABEL_KEYS i18n map, disables all buttons during in-flight RPC, dispatches markRead/clearNotificationActions on success, shows localized error on failure; NotificationCenter filters coreActionItems and renders cards at top; empty-state condition updated; tests cover all interaction paths including RPC dispatch, Redux state mutations, disabled state, empty actions, and unread indicator.
Frontend: MeetingSettingsPanel, routing, and settings registry
app/src/components/settings/panels/MeetingSettingsPanel.tsx, app/src/components/settings/panels/__tests__/MeetingSettingsPanel.test.tsx, app/src/pages/Settings.tsx, app/src/components/settings/settingsRouteRegistry.ts, app/src/components/settings/hooks/useSettingsNavigation.ts, app/src/components/settings/layout/settingsNavIcons.tsx
MeetingSettingsPanel loads/persists four settings via Tauri with sequence-ref ordering guard; renders policy selects and boolean toggles; non-Tauri and loading branches included; wired into Settings router and SETTINGS_ROUTE_REGISTRY under connections nav group; SettingsRoute union and nav icons updated; tests cover load, change handlers, non-Tauri, errors, and saved states.
Frontend: i18n translations across 14 locales
app/src/lib/i18n/en.ts, app/src/lib/i18n/ar.ts, app/src/lib/i18n/bn.ts, app/src/lib/i18n/de.ts, app/src/lib/i18n/es.ts, app/src/lib/i18n/fr.ts, app/src/lib/i18n/hi.ts, app/src/lib/i18n/id.ts, app/src/lib/i18n/it.ts, app/src/lib/i18n/ko.ts, app/src/lib/i18n/pl.ts, app/src/lib/i18n/pt.ts, app/src/lib/i18n/ru.ts, app/src/lib/i18n/zh-CN.ts
notifications.meeting.* and settings.meetings.* keys added covering join modes (listen-only vs active), skip/always-join options, action error message, auto-join/auto-summarize policies, listen-only mode, and transcript ingestion descriptions.

Sequence Diagram(s)

sequenceDiagram
  participant Heartbeat as Heartbeat Planner
  participant Collectors as extract_meeting_url_from_map
  participant Calendar as handle_calendar_meeting_candidate
  participant EventBus as DomainEvent Bus
  participant Redux as Redux / NotificationSlice
  participant Card as CoreNotificationCard
  participant RPC as agent_meetings_notification_action

  Heartbeat->>Collectors: collect_calendar_meetings(events)
  Collectors-->>Heartbeat: PendingEvent { meeting_url: Some(...) }
  Heartbeat->>Calendar: handle_calendar_meeting_candidate(meet_url, title)
  alt AskEachTime policy
    Calendar->>EventBus: publish MeetingSessionCreated
    Calendar->>Redux: dispatch CoreNotificationEvent { actions: [join, skip, alwaysJoin] }
    Calendar-->>Heartbeat: owns_notification = true
    Redux-->>Card: render action buttons
    Card->>RPC: openhuman.agent_meetings_notification_action { action_id, payload }
    RPC-->>Card: ok
    Card->>Redux: dispatch markRead + clearNotificationActions
  else Always policy
    Calendar->>EventBus: publish MeetingAutoJoinTriggered
    Calendar->>Calendar: spawn auto_join_meeting(listen_only_default)
    Calendar-->>Heartbeat: owns_notification = true
  else Never policy
    Calendar-->>Heartbeat: owns_notification = false
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • tinyhumansai/openhuman#3516: Introduced the NotificationAction/NotificationItem.actions data model and CoreNotificationEvent.actions backend support that this PR's CoreNotificationCard and Redux clearNotificationActions directly consume.
  • tinyhumansai/openhuman#3575: Both PRs extend SettingsRoute in useSettingsNavigation.ts with new route literals and add entries to SETTINGS_ROUTE_REGISTRY.
  • tinyhumansai/openhuman#3363: Both PRs modify the agent-meetings join flow through agent_meetings/ops.rs and meet settings schemas, touching the same call plumbing and event handling paths for meeting session orchestration.

Suggested reviewers

  • graycyrus
  • sanil-23

🐇 Hippity-hop, a meeting's in sight,
The calendar pings — join left or right!
"Ask each time," the rabbit proclaims,
Skip, always-join — or just listen to frames.
Config saves, the buttons appear,
OpenHuman hops in… the meeting is here! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main feature being implemented: calendar-triggered auto-join prompt functionality and Meeting Assistant settings UI, which aligns with the primary objectives and changes in the codebase.
Linked Issues check ✅ Passed The pull request successfully implements all major objectives from issues #3507 and #3511, including the bug fix for lost Meet URLs, auto-join policy routing, notification actions, settings panel with full i18n support across 14 locales, and comprehensive test coverage for both backend and frontend components.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: meeting URL extraction, auto-join policy implementation, notification actions, settings UI, i18n translations, and supporting infrastructure. No extraneous or unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@YellowSnnowmann YellowSnnowmann marked this pull request as ready for review June 16, 2026 13:02
@YellowSnnowmann YellowSnnowmann requested a review from a team June 16, 2026 13:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45d6a77dd4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/components/notifications/NotificationCenter.tsx
Comment thread src/openhuman/subconscious/heartbeat/planner/collectors.rs Outdated
…ficationCard

Implemented tests to verify the behavior of action buttons in the CoreNotificationCard component. Added scenarios to ensure buttons are cleared after successful actions and retained when RPC calls fail. This enhances the reliability of user interactions with notifications and prevents duplicate actions.
@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. labels Jun 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
src/openhuman/config/ops/ui.rs (1)

221-232: ⚡ Quick win

Add debug diagnostics for the new meet-settings apply branches.

Line 221 onward introduces new state-transition branches, but this function still has no entry/exit/branch diagnostics. Please add structured debug logs around the new policy/flag updates so runtime behavior is traceable during settings incidents.

As per coding guidelines, “Include debug logging with verbose diagnostics on all new/changed flows...”.

Suggested patch
 pub async fn apply_meet_settings(
     config: &mut Config,
     update: MeetSettingsPatch,
 ) -> Result<RpcOutcome<serde_json::Value>, String> {
+    tracing::debug!(
+        auto_orchestrator_handoff = ?update.auto_orchestrator_handoff,
+        auto_join_policy = ?update.auto_join_policy,
+        auto_summarize_policy = ?update.auto_summarize_policy,
+        listen_only_default = ?update.listen_only_default,
+        ingest_backend_transcripts = ?update.ingest_backend_transcripts,
+        "[config][meet] apply_meet_settings enter"
+    );
     if let Some(enabled) = update.auto_orchestrator_handoff {
         config.meet.auto_orchestrator_handoff = enabled;
     }
@@
     if let Some(ingest) = update.ingest_backend_transcripts {
         config.meet.ingest_backend_transcripts = ingest;
     }
+    tracing::debug!(
+        auto_orchestrator_handoff = config.meet.auto_orchestrator_handoff,
+        auto_join_policy = ?config.meet.auto_join_policy,
+        auto_summarize_policy = ?config.meet.auto_summarize_policy,
+        listen_only_default = config.meet.listen_only_default,
+        ingest_backend_transcripts = config.meet.ingest_backend_transcripts,
+        "[config][meet] apply_meet_settings updated"
+    );
     config.save().await.map_err(|e| e.to_string())?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/config/ops/ui.rs` around lines 221 - 232, Add structured debug
logging around the new meet-settings configuration updates starting at line 221.
For each of the four conditional branches (auto_join_policy,
auto_summarize_policy, listen_only_default, and ingest_backend_transcripts),
insert debug log statements that capture and display the values being set to
config.meet. These logs should provide verbose diagnostics about what
configuration values are being applied so that runtime behavior can be traced
during settings incidents, following the coding guideline to include debug
logging on all new/changed flows.

Source: Coding guidelines

app/src/components/settings/panels/MeetingSettingsPanel.tsx (1)

5-11: ⚡ Quick win

Import isTauri from the canonical module path.

Based on learnings, settings panel components should import isTauri from the canonical module app/src/utils/tauriCommands/common rather than the barrel export. Update the relative path to '../../../utils/tauriCommands/common'.

📦 Proposed fix
 import { useT } from '../../../lib/i18n/I18nContext';
 import {
-  isTauri,
   type MeetAutoJoinPolicy,
   type MeetAutoSummarizePolicy,
   openhumanGetMeetSettings,
   openhumanUpdateMeetSettings,
 } from '../../../utils/tauriCommands';
+import { isTauri } from '../../../utils/tauriCommands/common';
 import PanelPage from '../../layout/PanelPage';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/MeetingSettingsPanel.tsx` around lines 5 -
11, The import statement is currently importing `isTauri` from the barrel export
`'../../../utils/tauriCommands'`, but it should be imported from the canonical
module path instead. Update the import to bring `isTauri` from
`'../../../utils/tauriCommands/common'` while keeping the other imports
(`MeetAutoJoinPolicy`, `MeetAutoSummarizePolicy`, `openhumanGetMeetSettings`,
`openhumanUpdateMeetSettings`) from the original barrel export path
`'../../../utils/tauriCommands'`. This will require splitting the existing
import statement into two separate import statements.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/notifications/CoreNotificationCard.tsx`:
- Around line 18-27: The relativeTime function contains hardcoded English time
unit labels ("s ago", "m ago", "h ago", "d ago") that will display in English
regardless of the user's locale. Refactor this function to use the i18n pattern
from the repository: import and use the useT() hook from
app/src/lib/i18n/I18nContext, replace each hardcoded time label string with a
call to t() using appropriate translation keys, and add corresponding
translation entries to the en.ts locale file and all other locale files in the
repository to support multiple languages.
- Line 5: The CoreNotificationCard component is importing and using callCoreRpc
directly to make core RPC calls, which violates project policy requiring all
frontend core RPC calls to go through the relay contract. Remove the direct
import of callCoreRpc from coreRpcClient and instead create or use an existing
typed command wrapper in app/src/utils/tauriCommands/* that invokes the
core_rpc_relay Tauri command. Replace the direct callCoreRpc call in the
component (around lines 68-71) with a call to this typed wrapper function,
ensuring that the relay path is used for all core RPC communication to maintain
centralized transport and auth handling.

In `@app/src/lib/i18n/fr.ts`:
- Around line 3178-3182: The translation for the
'notifications.meeting.joinActive' key in the fr.ts file uses wording that
suggests sending a message rather than joining as an active participant. Replace
the current value 'Rejoindre et répondre' with 'Rejoindre en participant' to
make it clearer that the action is about joining as an active participant in the
meeting, not just responding with a message.

In `@app/src/lib/i18n/pt.ts`:
- Around line 3162-3166: The skip-button label for the meeting notification in
the notifications.meeting.skip key currently uses "Esta não" which reads like a
sentence fragment and creates an awkward notification action. Replace this value
with a short imperative label such as "Pular" or "Ignorar" to provide a clear,
direct action label for users.

In `@app/src/lib/i18n/ru.ts`:
- Line 3138: The translation for 'notifications.meeting.skip' uses 'Не эта'
which reads like a conversational reply rather than an action button label.
Replace this value with 'Пропустить' (which is the value used in common.skip) to
provide a clearer, more consistent button label that matches the rest of the UI
copy.

In `@src/openhuman/agent_meetings/calendar.rs`:
- Around line 170-180: When config loading fails in the error handling branch
(Err(e)), the code only publishes the legacy MeetAutoJoinPrompt event but does
not emit a CoreNotificationEvent with action buttons. Since the function returns
true, the heartbeat planner suppresses its plain notification, leaving no
actionable notification surface for the user. In addition to the existing
publish_global call for MeetAutoJoinPrompt, add another publish_global call in
this same error branch to emit the CoreNotificationEvent with appropriate action
buttons, ensuring users receive an actionable notification when config loading
fails.
- Around line 256-291: The code publishes a CoreNotificationEvent with action
buttons unconditionally after attempting store::create_session, even when the
session creation fails. Since the action handlers depend on the session existing
in the store, users can receive buttons that will fail against missing state.
Restructure the code so that the publish_global call and the entire
publish_core_notification block (including the action_payload setup and action
closure) only execute when store::create_session succeeds, not unconditionally
after the error is logged.

---

Nitpick comments:
In `@app/src/components/settings/panels/MeetingSettingsPanel.tsx`:
- Around line 5-11: The import statement is currently importing `isTauri` from
the barrel export `'../../../utils/tauriCommands'`, but it should be imported
from the canonical module path instead. Update the import to bring `isTauri`
from `'../../../utils/tauriCommands/common'` while keeping the other imports
(`MeetAutoJoinPolicy`, `MeetAutoSummarizePolicy`, `openhumanGetMeetSettings`,
`openhumanUpdateMeetSettings`) from the original barrel export path
`'../../../utils/tauriCommands'`. This will require splitting the existing
import statement into two separate import statements.

In `@src/openhuman/config/ops/ui.rs`:
- Around line 221-232: Add structured debug logging around the new meet-settings
configuration updates starting at line 221. For each of the four conditional
branches (auto_join_policy, auto_summarize_policy, listen_only_default, and
ingest_backend_transcripts), insert debug log statements that capture and
display the values being set to config.meet. These logs should provide verbose
diagnostics about what configuration values are being applied so that runtime
behavior can be traced during settings incidents, following the coding guideline
to include debug logging on all new/changed flows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 280a0c02-cbe5-4659-81e7-b696684e52c9

📥 Commits

Reviewing files that changed from the base of the PR and between eae73c9 and ea341a8.

📒 Files selected for processing (41)
  • app/src/components/notifications/CoreNotificationCard.test.tsx
  • app/src/components/notifications/CoreNotificationCard.tsx
  • app/src/components/notifications/NotificationCenter.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts
  • app/src/components/settings/layout/settingsNavIcons.tsx
  • app/src/components/settings/panels/MeetingSettingsPanel.tsx
  • app/src/components/settings/panels/__tests__/MeetingSettingsPanel.test.tsx
  • app/src/components/settings/settingsRouteRegistry.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/nativeNotifications/__tests__/service.test.ts
  • app/src/lib/nativeNotifications/service.ts
  • app/src/pages/Settings.tsx
  • app/src/store/notificationSlice.ts
  • app/src/test/setup.ts
  • app/src/utils/tauriCommands/config.test.ts
  • app/src/utils/tauriCommands/config.ts
  • src/core/event_bus/events.rs
  • src/core/event_bus/events_tests.rs
  • src/openhuman/agent_meetings/calendar.rs
  • src/openhuman/config/ops/ui.rs
  • src/openhuman/config/ops_tests.rs
  • src/openhuman/config/schemas/controllers.rs
  • src/openhuman/config/schemas/helpers.rs
  • src/openhuman/config/schemas/schema_defs.rs
  • src/openhuman/subconscious/heartbeat/planner/collectors.rs
  • src/openhuman/subconscious/heartbeat/planner/mod.rs
  • src/openhuman/subconscious/heartbeat/planner/persistence.rs
  • src/openhuman/subconscious/heartbeat/planner/types.rs

Comment thread app/src/components/notifications/CoreNotificationCard.tsx
Comment thread app/src/components/notifications/CoreNotificationCard.tsx
Comment thread app/src/lib/i18n/fr.ts
Comment thread app/src/lib/i18n/pt.ts
Comment thread app/src/lib/i18n/ru.ts Outdated
Comment thread src/openhuman/agent_meetings/calendar.rs Outdated
Comment thread src/openhuman/agent_meetings/calendar.rs
…uguese, and Russian

Revised translations for the meeting notification actions to improve clarity. Updated 'joinActive' in French to 'Rejoindre en participant', 'skip' in Portuguese to 'Ignorar', and 'skip' in Russian to 'Пропустить'. This enhances the user experience by providing more accurate language in notifications.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026
…ith URLs

Refined the test for extracting imminent calendar meetings to ensure that the  is correctly populated and that the planner marks the event with . This change clarifies the expected behavior of the auto-join policy and improves the reliability of the test by focusing on the extraction logic without relying on external configurations.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026
This update introduces functions to extract and utilize the meeting owner's display name from calendar event payloads, improving the auto-join functionality. The  function now accepts an optional owner display name, allowing for more accurate replies. Additionally, the notification handling logic has been updated to incorporate the reply anchor from action payloads, ensuring that the bot can respond appropriately based on the context of the meeting. Tests have been added to verify the correct behavior of these enhancements.
@sanil-23

Copy link
Copy Markdown
Contributor

Code review — calendar-triggered auto-join + Meeting Assistant settings

Walkthrough. Adds a calendar-driven Meet auto-join flow: the heartbeat planner forwards imminent meetings to handle_calendar_meeting_candidate, which routes by auto_join_policy (Never/Always/AskEachTime), surfaces an actionable CoreNotificationCard in AskEachTime mode, and wires up a new Meeting Assistant settings panel + agent_meetings_notification_action RPC. The work is well-tested (URL extraction edge cases, policy ownership, payload builders, notification-action variants, settings apply, Vitest for the card/panel/service) and i18n parity is complete (27 keys × 14 locales). Overall solid; a few correctness/consistency items below.

Changed-files summary (highlights)

Area Files Notes
Core routing agent_meetings/calendar.rs, agent_meetings/ops.rs candidate handler, policy routing, action handler, payload builders
Heartbeat planner/{mod,collectors,types,persistence}.rs meeting_url plumbing + forwarding/suppression
Config config/ops/ui.rs, config/schemas/{controllers,helpers,schema_defs}.rs MeetSettingsPatch extension + RPC
Frontend CoreNotificationCard.tsx, MeetingSettingsPanel.tsx, notificationSlice.ts, nativeNotifications/service.ts, settings wiring actionable card + settings panel
i18n 14 locale files full parity

Major

1. Always policy has no per-meeting dedup → duplicate bot:join.
handle_calendar_meeting_candidate (calendar.rs, Always branch) spawns auto_join_meeting with a fresh correlation_id every call and creates no session — unlike the AskEachTime branch, which dedups via store::get_session_by_meet_url. The same imminent meeting reaches this function from multiple callers:

  • The heartbeat planner forwards on two allow_external stages — final_call (0–10 min) and starting_now (0 to −10 min) in planner/plan.rs:34-57. Each is a distinct dedupe_key (keyed on stage), so mark_sent lets both through across ticks.
  • The live Composio MeetCalendarSubscriber also calls it directly.

Net effect with auto_join_policy = Always: the bot can be told to join the same meeting 2+ times with different correlation IDs (so the backend can't dedup by correlation). Suggest mirroring the AskEachTime dedup in the Always branch — e.g. create/check a session (or a meet_url-keyed marker) before spawning the join:

AutoJoinPolicy::Always => {
    if let Ok(Some(existing)) = store::get_session_by_meet_url(&config, &meet_url) {
        if existing.status != MeetingSessionStatus::Ended {
            tracing::debug!(meeting_id = %existing.id, "[meet:calendar] already auto-joined — skipping");
            return false;
        }
    }
    // ...create a session, then spawn auto_join_meeting...
}

Major (pre-existing, now load-bearing)

2. Composio path extract_meet_url leaks free-form location strings — the exact case collectors.rs just fixed.
collectors.rs correctly added extract_meeting_url_from_text so a location like "Zoom Meeting: https://zoom.us/j/123" yields only the parseable URL (with a comment explaining that returning the whole string produces a meeting_url the join handler rejects). But the sibling Composio path extract_meet_url in calendar.rs:574-579 still returns the whole location string:

if let Some(loc) = root.get("location").and_then(|v| v.as_str()) {
    if is_meeting_url(loc) {
        return Some(loc.to_string());   // returns "Zoom Meeting: https://zoom.us/j/123"
    }
}

That string becomes the meetUrl in the AskEachTime action payload; when the user clicks Join, handle_notification_action → handle_join → validate_meeting_url runs url::Url::parse and fails. The buttons silently never work for Zoom/Teams-in-location events arriving via Composio. Reuse the same token-scan helper here (see nitpick #3 for de-dup).

Minor / nitpicks

3. MEETING_HOST_PATTERNS + is_meeting_url triplicated. Now defined in calendar.rs:532-541, collectors.rs:436-441, and as ALLOWED_HOSTS in ops.rs:21-26. Consider a single shared helper (and have both extractors funnel free-form text through one extract_meeting_url_from_text), which would also fix #2 for free.

4. extract_meeting_url_from_text doesn't strip a trailing period. The trim set covers ()[]<>,;"' but not ., so …link: https://zoom.us/j/123. keeps the trailing dot (which url::Url::parse accepts as path), yielding a slightly-off URL. Add . to the trim set.

Questions

  • For Never, the generic "meeting starting" plain card still fires (by design per the doc comment). Intended that a user who set Never still gets heads-up cards? (Seems fine, just confirming.)

Looks good

  • AskEachTime session-dedup, fail-closed fallbacks (config-load failure / session-create failure → plain reminder, returns false so the planner still notifies), and the effective_listen_only "no anchor ⇒ listen-only" safety rule are all well-reasoned and tested.
  • CoreNotificationCard guards double-clicks (pendingActionId) and clears actions after success to prevent re-firing bot:join/always_join.
  • auto_join_policy / auto_summarize_policy RPC parsing rejects unknown values with a clear error.
  • i18n parity complete; settings route/icon/registry wiring consistent.

Read-only review — no changes pushed.

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — gated on the correctness items from my detailed review.

Must fix

  1. Always policy duplicate bot:join (agent_meetings/calendar.rs, Always branch). No per-meeting dedup, unlike AskEachTime. The same meeting reaches the handler from two allow_external heartbeat stages (final_call + starting_now, planner/plan.rs:34-57) and the live Composio subscriber, each with a fresh correlation_id — so a user on "Always join" can have the bot join the same call 2+ times. Mirror the AskEachTime session dedup before spawning the join.

Should fix (broken path this PR introduces buttons for)
2. Composio extract_meet_url leaks free-form location strings (calendar.rs:574-579). Returns the whole location (e.g. "Zoom Meeting: https://zoom.us/j/123"), which becomes the meetUrl in the AskEachTime action payload and then fails validate_meeting_url on click. collectors.rs already added extract_meeting_url_from_text for exactly this case — reuse it here.

Nitpicks #3 (triplicated MEETING_HOST_PATTERNS/is_meeting_url) and #4 (trailing . not trimmed) are non-blocking.

Otherwise the feature is well-structured and well-tested — fail-closed fallbacks, the listen-only safety rule, double-click guards, strict RPC enum parsing, and full i18n parity all look good. Happy to re-review once #1 is addressed.

@graycyrus graycyrus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YellowSnnowmann sanil-23's CHANGES_REQUESTED is blocking and I'm deferring to it — both items are real correctness bugs that need to be addressed first:

  1. The Always policy has no per-meeting session dedup, unlike AskEachTime. With two heartbeat stages (final_call + starting_now) and the live Composio subscriber each firing independently with fresh correlation_id values, a user on Always can end up with the bot joining the same meeting 2-3 times. The fix is the same session-by-meet-url guard that AskEachTime already uses before spawning the join.

  2. The Composio extract_meet_url path (the old subscriber branch in calendar.rs) still returns the raw location string rather than extracting a URL out of it. collectors.rs in this very PR added extract_meeting_url_from_text exactly for this — that function should be reused here, otherwise free-form location fields like "Zoom Meeting: https://zoom.us/j/123" become the meetUrl in the AskEachTime payload and then fail validation when the user actually clicks Join.

Please address those two before I do a full approval pass.


One additional issue I found that neither sanil-23 nor CodeRabbit flagged:

[major] MeetingSettingsPanel: no state rollback on persist failure

All four handlers (handleAutoJoinChange, handleAutoSummarizeChange, handleListenOnlyChange, handleIngestChange) use an optimistic-update pattern — local state updates immediately, then persist() fires. If the RPC fails, the error note appears but the component state is not restored. The select/toggle shows the new value while the backend still holds the old one. A user who notices the error and doesn't manually re-change the setting is left with a UI that lies about their configuration.

Fix: capture the prior value and restore it in the catch:

const handleAutoJoinChange = (next: MeetAutoJoinPolicy) => {
  const prev = autoJoin;
  setAutoJoin(next);
  void persist({ auto_join_policy: next }).catch(() => setAutoJoin(prev));
};

Same pattern for the other three handlers.

Two minor notes (non-blocking):

  • MeetingSessionCreated.source is a free-form string. The three valid values ("calendar", "manual", "api") live only in a comment — a CalendarSessionSource enum would make this compile-time safe.
  • MEETING_HOST_PATTERNS misses teams.live.com (Teams Free / Teams Personal). Not blocking, easy follow-on.

Apart from the above, the implementation is genuinely well-structured: the AskEachTime session dedup, the listen-only fallback when no reply anchor resolves, the double-click guard and action-clear in CoreNotificationCard, the sequenced persistSeqRef in the settings panel, the full 14-locale i18n coverage, and the pure helper functions (build_notification_join_map, build_action_payload, extract_meeting_url_from_text) that make the logic unit-testable without live infrastructure — all of these are done right. Good work on the overall feature shape.

Comment thread app/src/components/settings/panels/MeetingSettingsPanel.tsx
@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

Addressed all four items from sanil-23's review (pending commit + push):

Major #1 — Always policy dedup: Added store::get_session_by_meet_url guard before spawning auto_join_meeting. If a non-Ended session already exists for the URL, we return early. Also create a Joined session (keyed by correlation_id) before spawning, so subsequent heartbeat ticks and Composio re-fires find the existing entry and skip. Session-create failure logs a warning but proceeds (best-effort dedup).

Major #2 — extract_meet_url location leaks free-form string: The location branch now calls extract_meeting_url_from_text (a new local helper mirroring the one in collectors.rs) instead of returning the raw loc string. Free-form locations like "Zoom Meeting: https://zoom.us/j/123" now yield only the parseable URL.

Minor #3 — MEETING_HOST_PATTERNS triplicated: Intentionally deferred as a follow-up refactor — consolidating across calendar.rs, collectors.rs, and ops.rs would cross domain boundaries and is pure cleanup. The correctness fixes (#1 and #2) are self-contained.

Minor #4extract_meeting_url_from_text missing trailing period: Added . to the trim_matches set in both collectors.rs and the new calendar.rs copy.

New tests cover: free-form label+URL extraction, paren/bracket stripping, trailing-period stripping, non-meeting locations, and the integration-level extract_meet_url with free-form location fields.

…ity on persist failure

This update modifies the MeetingSettingsPanel to include rollback functionality for auto-join, auto-summarize, listen-only, and transcript ingestion settings when the persist operation fails. The changes ensure that users' selections revert to their previous values in case of an error during the save process. Additionally, new tests have been added to verify this behavior, enhancing the reliability of the settings management.
@Felyx-Fu

Copy link
Copy Markdown
Contributor

I re-checked the current head (af621df) against the blocking review items. The two correctness blockers appear addressed in code now:\n\n- Always-policy duplicate joins: src/openhuman/agent_meetings/calendar.rs now checks store::get_session_by_meet_url(&config, &meet_url) before spawning �ot:join, and skips when an active non-ended session exists.\n- Composio free-form location: �xtract_meet_url now routes the location field through �xtract_meeting_url_from_text, returning only a parseable http(s) meeting URL token. There are tests for free-form Zoom and Teams location strings.\n\nCurrent CI on #3721 is green, including Rust Quality, Rust Core/Tauri Coverage, Rust E2E, Frontend Quality/Coverage, Playwright shards, and Coverage Gate. The only unresolved GraphQL thread I see is the MeetingSettingsPanel rollback comment, which the current head also addresses with rollback callbacks and tests per the author reply.\n\nRequesting a reviewer re-check on the stale CHANGES_REQUESTED state.

@YellowSnnowmann YellowSnnowmann merged commit 5265525 into tinyhumansai:main Jun 17, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Meeting Assistant] PR-5: Settings UI [Meeting Assistant] PR-1: Calendar-triggered auto-join prompt

4 participants